
    #include <iostream>

    using namespace std;

    /* 引用的本质是一个指针常量，引用分为普通引用和常引用，常引用一般会对其权限进行改变，为缩小，常引用就是定义了一个指向常量的指针常量 const int * const p
    * 
    */

    /* 首先，左值和右值是表达式的属性。C++中的表达式，不是左值就是右值。左值可以位于赋值语句的左侧，而右值则不能；可以做一个简单的归纳：
    * 当一个对象被用作右值的时候，用的是对象的值；
    * 当对象被用作左值的时候，用的是对象的身份(在内存中的位置）。需要右值的地方可以用左值代替，但不能把右值当成左值使用。
    * 变量都是左值，
    */

    /*
    * 左值引用和右值引用
    * 左值引用是常规的引用:变量都是左值
    * 右值引用是必须绑定到右值的引用,他有着与左值引用完全相反的绑定特性，我们通过 && 来获得右值引用。右值有一个重要的性质——只能绑定到一个将要销毁的对象上。
    * 
    * 左值拥有持久的状态，而右值要么是字面常量，要么是在表达式求职过程中创建的临时对象。
    右值引用的对象将要被销毁。不能将一个右值引用绑定到一个右值引用类型的变量上，因为变量都是左值。
    */

    /*
    万能引用（Universal Reference）是指模板参数使用 && 时，能够接受任何类型的引用，包括左值引用和右值引用。右值引用是指绑定到右值的引用，可以实现移动语义和完美转发。

    区别：

    万能引用是模板参数的一种表达方式，而右值引用是一种变量类型。
    万能引用可以接受任何类型的引用，包括左值引用和右值引用，而右值引用只能接受右值引用。
    万能引用在模板函数中用于实现完美转发，而右值引用主要用于实现移动语义。
    万能引用的声明方式为 T&&，而右值引用的声明方式为 X&&，其中 T 和 X 都表示类型。
    */
    int c = 0;
    // 
    int & add(int a, int b)
    {
    c = a+b;
    return c;
    }

    // 返回值可以用 右值引用，解决深拷贝的问题
    int sub(int a, int b)
    {
    return a+b;
    }

    /*万能引用跟右值引用形式一样，但其不需要确定的类型，可以接收左值或者右值，返回左值引用或者右值引用，用在推断类型的场合和用在模板
    *
    *
    * 
    */
    template<typename T>
    void tempFun(T && t)
    {
    // t = 40;
    cout << t << endl;
    }

    int tempFun1(int && a, int && b)
    {
    return a+b;
    }

    // 区分万能引用和右值引用
    /*
    *  如果函数模板形参具备T&&型别，并且T的型别是推导而来，或如果对象使用auto&&声明其类型，则该形参或对象就是万能引用
    *  如果型别声明不精确地具备type&&的形式，或者型别推导并未发生，则type&&就代表右值引用
    *  若采用右值来初始化万能引用，就会得到一个右值引用，如果采用左值来初始化，则会得到一个左值引用。
    */

    /*
    完美转发:
    通过函数模板调用另外一个函数，模板的万能引用既可以接收左值也可以接收右值，但是对于函数内部来说，不管接收的是左值还是右值，模板函数内部对于形参都是左值(T && t1 ,t1本身是左值)
    如果函数的第一个参数需要右值，必须这样调用f(std::move(t1),t2)，但是模板是通用的，我们不能直接将std::move()来写死，这样就不能调用接收左值的函数了
    */
    template<typename T, typename U = int> // 仅仅是默认使用int类型，也可以更改
    void f(T && t1, U && t2)
    {
    cout << t1+t2 << endl;
    }
    // 完美转发
    template<typename F ,typename T, typename U>
    void testFun(F f, T && t1, U && t2)
    {
    f(std::forward<T>(t1),std::forward<U>(t2));
    }
    // void gu(int & t1, int & t2)
    // {
    //     cout << t1+t2 << endl;
    // }
    void gu_y(int && t1, int & t2)
    {
    cout << t1+t2 << endl;
    }


    class print //print类名称 
    {
        public://公共，一般为函数的接口 
            void f(int a, int b)//函数，必须在类的内部声明，但是定义在内部和外部都可以进行 ,这是在内定义 
            {
                c = a - b;
            }
            void g();

            void h();

        protected:


        private: //私有，仅在类的内部使用 ，相当于在类的内部的全局 
            int a;
            int b;
            int c;
    };//记得最后的这个分号不能掉 

    void print::g()
    {
        cout << "c:" << c << endl;
    } 

    void print::h() //外部定义格式：返回类型+类的名称+"::"+函数的定义 
    {
        c = c * 2;
    }


    int main (int argc,char *argv[])
    {

    int a = 10;

    // const int &p = a; //定义后不能对其进行修改
    int &p = a;// a是左值

    // int &p = a*10;// a*10是右值

    p = 111;

    printf("左值引用: Value: %d %d \n",a,p++);// p++是先赋值，再+1

    int && p1 = 15;

    // int && p1 = a; 右值引用不可以引用左值
    // 可以显示的将一个左值转换成对应的右值引用类型。通过move函数来执行
    p1 = std::move(a);
    int ji = 90;
    // int & p1 = 10;  // 10是右值

    printf("右值引用: Value: %d %d \n",p1,p1++);


    int hu_1 = 90;
    int hu_2 = 91;

    int & hu_3 = add(hu_1,hu_2);

    printf("Add: %d \n",hu_3);

    int ki_1 = 34;
    int ki_2 = 12;

    int && ki_3 = sub(ki_1,ki_2);
    printf("Sub: %d \n",ki_3);


    int lo = 5;

    auto && v1 = lo;//auto 为 int ，v1的类型为int &
    auto && v2 = 6;// auto 为int，v2的类型为int && 

    tempFun(lo);

    tempFun(100);
    tempFun(v1);
    tempFun(v2);

    auto && lo1 = tempFun1(19,3);// auto && lo1 和 auto lo1 都可以，前者是万能引用

    cout << lo1 << endl;

    // c++实例化是在编译阶段完成的，实例化指的是，具体的函数定义，比如模板会确定数据类型和函数类型
    {
        print hu;
        hu.f(2,3);

        hu.g();
        hu.h();

    }

    /* 完美转发
        * 完美转发的目的是使用函数函数模板调用另一个函数时，希望既可以接收左值也可以接收右值
        *
    */

    int per1 = 23;
    int per2 = 34;

    f(per1,per2);
    testFun(gu_y,23,per2);// 传入右值和左值


    int lp = 10;

    // lp++返回的是lp+1,++lp返回的是lp,因此对于lp++是先lp,赋值，

    cout << lp << " " << lp++ << " " << ++lp << " " << lp << endl;

    // 指令的读取基本是从左到右的因此是，先++

    int & ko1 = ++lp;// 前置自增为左值
    int && ko2 = lp++; // 后置自增为右值

    //语义转发
    int && ko = std::move(lp);



    return 0;
    }



